package com.zy.gateway.filters;

import com.alibaba.cloud.commons.lang.StringUtils;
import com.alibaba.nacos.shaded.com.google.gson.Gson;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.Data;
import lombok.extern.java.Log;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @program: shopping153_cloud
 * @description:
 * @author: zy
 * @create: 2025-08-29 18:43
 */
@Component
@Log
public class GlobalTokenFilter  implements GlobalFilter, Ordered {

    @Value("${token.secret}")
    private String secret;
    @Value("${token.expiration}")
    private long expiration; // in milliseconds

    private final AppProperties appProperties;
    private final AntPathMatcher pathMatcher = new AntPathMatcher(); // 路径匹配器
    private final Gson gson = new Gson();

    public GlobalTokenFilter(AppProperties appProperties) {
        this.appProperties = appProperties;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        String path = request.getURI().getPath();
        log.info("Request path: " + path);
        boolean isToCheckPath = false;
        for (String checkTokenPath : appProperties.getCheckTokenPaths()) {
            if (pathMatcher.match(checkTokenPath, path)) {  //访问路径path是否为要检查token的路径
                isToCheckPath = true;
                break;
            }
        }
        if (!isToCheckPath) {
            log.info("Not to check token path: " + path);
           return  chain.filter(   exchange);   //放行
        }
        String token = request.getHeaders().getFirst("token");  // 在请求头中取  header中的  "token"
        if (StringUtils.isEmpty(token)) {
            log.warning("No token found in request headers for path: " + path+",this request is not authorized");
            response.setStatusCode(HttpStatus.UNAUTHORIZED); // Set HTTP status to 401
            Map<String, Object> map = new HashMap<>();
            map.put("code", 0);
            map.put("msg", "未登录或登录信息缺失");
            return writeResponse(response, map); // Use the refactored writeResponse
        }

        try {
            Key signingKey = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret));
            Jws<Claims> jwsClaims = Jwts.parserBuilder()
                    .setSigningKey(signingKey) // 使用解码后生成的 Key
                    .build()
                    .parseClaimsJws(token);
            Claims claims = jwsClaims.getBody();
            Date expiration = claims.getExpiration();
            Date now = new Date();  //当前
            if (expiration != null && expiration.before(now)) {
                log.warning("Token已过期！Path: " + path);
                response.setStatusCode(HttpStatus.UNAUTHORIZED); // Set HTTP status to 401
                Map<String, Object> map = new HashMap<>();
                map.put("code", 0);
                map.put("msg", "token已过期，请重新登录");
                return writeResponse(response, map);
            }
            //从token中解析出来的信息如下:
            log.info("Token 有效 for path: " + path);
            log.info("用户名: " + claims.get("userName"));
            log.info("过期时间: " + expiration);
            log.info("产生时间: " + claims.get("iat"));
            log.info("用户Id: " + claims.get("userId"));
            log.info("角色 :" + claims.get("roles"));

            ServerHttpRequest.Builder builder = request.mutate();
            // Ensure claims are extracted as appropriate types
            Long userId = claims.get("userId", Long.class); // Assuming "id" claim is String, adjust if Long/Integer
            String username = claims.get("userName", String.class);
            Object rolesObj = claims.get("roles"); // Roles can be String or List<String>
            String roles = null;
            if (rolesObj instanceof List) {
                roles = String.join(",", (List<String>) rolesObj);
            } else if (rolesObj != null) {
                roles = rolesObj.toString();
            }
            if (roles != null && !roles.isEmpty()) {
                builder.header("X-User-Roles", roles);
            }

            if (userId != null) {
                builder.header("X-User-ID", userId.toString());
            }
            if (username != null) {
                builder.header("X-User-Name", username);
            }
            ServerHttpRequest newRequest = builder.build();
            ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
            return chain.filter(newExchange); // Proceed with the modified request

        }catch (ExpiredJwtException e) {
            // This catch block is technically redundant if `if (expiration != null && expiration.before(now))` handles it.
            // But it's good for explicit handling if the `parseClaimsJws` itself throws it directly.
            log.severe("Token 已过期 (ExpiredJwtException): " + path + ", Error: " + e.getMessage());
            response.setStatusCode(HttpStatus.UNAUTHORIZED); // Set HTTP status to 401
            Map<String, Object> map = new HashMap<>();
            map.put("code", 0);
            map.put("msg", "token已过期，请重新登录");
            return writeResponse(response, map);
        } catch (JwtException e) {
            e.printStackTrace();
            log.severe("Token 非法！(JwtException): " + path + ", Error: " + e.getMessage());
            response.setStatusCode(HttpStatus.FORBIDDEN); // Set HTTP status to 403
            Map<String, Object> map = new HashMap<>();
            map.put("code", 0);
            map.put("msg", "token无效或非法");
            return writeResponse(response, map);
        } catch (Exception e) {
            // Catch any other unexpected exceptions during token processing
            log.severe("处理Token时发生未知错误: " + path + ", Error: " + e.getMessage());
            response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR); // 500 Internal Server Error
            Map<String, Object> map = new HashMap<>();
            map.put("code", 0);
            map.put("msg", "服务器内部错误，请稍后再试");
            return writeResponse(response, map);
        }


    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE; // Ensure this filter runs first
    }


    private Mono<Void> writeResponse(ServerHttpResponse response, Object data) {
        response.getHeaders().add("Content-Type", "application/json;charset=utf-8");
        String resJson = gson.toJson(data); // Using the shared Gson instance
        DataBuffer dataBuffer = response.bufferFactory().wrap(resJson.getBytes());
        return response.writeWith(Flux.just(dataBuffer));
    }
}

@Component
@ConfigurationProperties(prefix = "rights")
@Data
class AppProperties {
    private List<String> checkTokenPaths;
}
